package org.apache.lucene.index;

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* Class that Posting and PostingVector use to write byte
 * streams into shared fixed-size byte[] arrays.  The idea
 * is to allocate slices of increasing lengths For
 * example, the first slice is 5 bytes, the next slice is
 * 14, etc.  We start by writing our bytes into the first
 * 5 bytes.  When we hit the end of the slice, we allocate
 * the next slice and then write the address of the new
 * slice into the last 4 bytes of the previous slice (the
 * "forwarding address").
 *
 * Each slice is filled with 0's initially, and we mark
 * the end with a non-zero byte.  This way the methods
 * that are writing into the slice don't need to record
 * its length and instead allocate a new slice once they
 * hit a non-zero byte. */

import java.util.Arrays;
import java.util.List;
import static org.apache.lucene.util.RamUsageEstimator.NUM_BYTES_OBJECT_REF;
import org.apache.lucene.util.ArrayUtil;

final class ByteBlockPool {

    abstract static class Allocator {
        abstract void recycleByteBlocks(byte[][] blocks, int start, int end);

        abstract void recycleByteBlocks(List<byte[]> blocks);

        abstract byte[] getByteBlock();
    }

    public byte[][] buffers = new byte[10][];

    int bufferUpto = -1; // Which buffer we are upto
    public int byteUpto = DocumentsWriter.BYTE_BLOCK_SIZE; // Where we are in head buffer

    public byte[] buffer; // Current head buffer
    public int byteOffset = -DocumentsWriter.BYTE_BLOCK_SIZE; // Current head offset

    private final Allocator allocator;

    public ByteBlockPool(Allocator allocator) {
        this.allocator = allocator;
    }

    public void reset() {
        if (bufferUpto != -1) {
            // We allocated at least one buffer

            for (int i = 0; i < bufferUpto; i++)
                // Fully zero fill buffers that we fully used
                Arrays.fill(buffers[i], (byte) 0);

            // Partial zero fill the final buffer
            Arrays.fill(buffers[bufferUpto], 0, byteUpto, (byte) 0);

            if (bufferUpto > 0)
                // Recycle all but the first buffer
                allocator.recycleByteBlocks(buffers, 1, 1 + bufferUpto);

            // Re-use the first buffer
            bufferUpto = 0;
            byteUpto = 0;
            byteOffset = 0;
            buffer = buffers[0];
        }
    }

    public void nextBuffer() {
        if (1 + bufferUpto == buffers.length) {
            byte[][] newBuffers = new byte[ArrayUtil.oversize(buffers.length + 1, NUM_BYTES_OBJECT_REF)][];
            System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);
            buffers = newBuffers;
        }
        buffer = buffers[1 + bufferUpto] = allocator.getByteBlock();
        bufferUpto++;

        byteUpto = 0;
        byteOffset += DocumentsWriter.BYTE_BLOCK_SIZE;
    }

    public int newSlice(final int size) {
        if (byteUpto > DocumentsWriter.BYTE_BLOCK_SIZE - size)
            nextBuffer();
        final int upto = byteUpto;
        byteUpto += size;
        buffer[byteUpto - 1] = 16;
        return upto;
    }

    // Size of each slice.  These arrays should be at most 16
    // elements (index is encoded with 4 bits).  First array
    // is just a compact way to encode X+1 with a max.  Second
    // array is the length of each slice, ie first slice is 5
    // bytes, next slice is 14 bytes, etc.
    final static int[] nextLevelArray = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 9 };
    final static int[] levelSizeArray = { 5, 14, 20, 30, 40, 40, 80, 80, 120, 200 };
    final static int FIRST_LEVEL_SIZE = levelSizeArray[0];

    public int allocSlice(final byte[] slice, final int upto) {

        final int level = slice[upto] & 15;
        final int newLevel = nextLevelArray[level];
        final int newSize = levelSizeArray[newLevel];

        // Maybe allocate another block
        if (byteUpto > DocumentsWriter.BYTE_BLOCK_SIZE - newSize)
            nextBuffer();

        final int newUpto = byteUpto;
        final int offset = newUpto + byteOffset;
        byteUpto += newSize;

        // Copy forward the past 3 bytes (which we are about
        // to overwrite with the forwarding address):
        buffer[newUpto] = slice[upto - 3];
        buffer[newUpto + 1] = slice[upto - 2];
        buffer[newUpto + 2] = slice[upto - 1];

        // Write forwarding address at end of last slice:
        slice[upto - 3] = (byte) (offset >>> 24);
        slice[upto - 2] = (byte) (offset >>> 16);
        slice[upto - 1] = (byte) (offset >>> 8);
        slice[upto] = (byte) offset;

        // Write new level:
        buffer[byteUpto - 1] = (byte) (16 | newLevel);

        return newUpto + 3;
    }
}
